BeanRelationFixer.java
package org.codefilarete.stalactite.sql.result;
import java.util.Collection;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.codefilarete.tool.Reflections;
import org.codefilarete.tool.function.TriConsumer;
/**
* An interface aimed at abstracting the way how relations between 2 beans are filled : implementation should handle one-to-one relation
* as well as one-to-many relation.
* Since implementations are quite simple, they are done through all "of" static methods in this interface.
*
* @param <E> bean type on which relation must be applied to
* @param <I> relation input type
*
* @author Guillaume Mary
* @see #of(BiConsumer, Function, Class)
* @see #of(BiConsumer, Function, Supplier)
* @see #of(BiConsumer)
*/
@FunctionalInterface
public interface BeanRelationFixer<E, I> {
/**
* Main method that fills the relation
*
* @param target the owner of the relation
* @param input the objet to be written/added into the relation
*/
void apply(E target, I input);
/**
* Shortcut to create a {@link BeanRelationFixer} for a one-to-one relation.
*
* @param setter the method that fixes the relation
* @return a {@link BeanRelationFixer} mapped to {@link BiConsumer#accept(Object, Object)}
*/
static <E, I> BeanRelationFixer<E, I> of(BiConsumer<E, I> setter) {
return of(setter, (a, b) -> { /* no bi-directional relation, nothing to do */ });
}
/**
* Shortcut to create a {@link BeanRelationFixer} for a bidirectional one-to-one relation.
*
* @param setter the method that fixes the relation
* @param reverseSetter the setter for the other side of the relation
* @return a {@link BeanRelationFixer} mapped to {@link BiConsumer#accept(Object, Object)}
*/
static <E, I> BeanRelationFixer<E, I> of(BiConsumer<E, I> setter, BiConsumer<I, E> reverseSetter) {
return (s, i) -> {
setter.accept(s, i);
// bidirectional assignment
reverseSetter.accept(i, s);
};
}
/**
* Shortcut to {@link #of(BiConsumer, Function, Supplier)} with a supplier that will instantiate the given concrete Collection class
*
* @param setter the method that sets the {@link Collection} onto the target bean
* @param getter the method that gets the {@link Collection} from the target bean
* @param concreteCollectionType the Class that will be instanced to fill the relation if it is null
* @return a {@link BeanRelationFixer} that will add the input to the Collection and create if the getter returns null
*/
static <E, I, C extends Collection<I>> BeanRelationFixer<E, I> of(BiConsumer<E, C> setter, Function<E, C> getter,
Class<? extends C> concreteCollectionType) {
return of(setter, getter, () -> Reflections.newInstance(concreteCollectionType));
}
/**
* Shortcut to create a {@link BeanRelationFixer} for a one-to-many relation where the attribute is a {@link Collection}
*
* @param setter the method that sets the {@link Collection} onto the target bean
* @param getter the method that gets the {@link Collection} from the target bean
* @param collectionFactory a supplier of an instance to fill the relation if it is null
* @return a {@link BeanRelationFixer} that will add the input to the Collection and create if the getter returns null
*/
static <E, I, C extends Collection<I>> BeanRelationFixer<E, I> of(BiConsumer<E, C> setter, Function<E, C> getter, Supplier<C> collectionFactory) {
return of(setter, getter, collectionFactory, (a, b) -> { /* no bi-directional relation, nothing to do */ });
}
/**
* Shortcut to {@link #of(BiConsumer, Function, Supplier, BiConsumer)} with a supplier that will instantiate the given concrete Collection class
*
* @param setter the method that sets the {@link Collection} onto the target bean
* @param getter the method that gets the {@link Collection} from the target bean
* @param concreteCollectionType the Class that will be instanced to fill the relation if it is null
* @return a {@link BeanRelationFixer} that will add the input to the Collection and create if the getter returns null
*/
static <E, I, C extends Collection<I>> BeanRelationFixer<E, I> of(BiConsumer<E, C> setter, Function<E, C> getter,
Class<? extends C> concreteCollectionType,
BiConsumer<I, E> reverseSetter) {
return of(setter, getter, () -> Reflections.newInstance(concreteCollectionType), reverseSetter);
}
/**
* Shortcut to create a {@link BeanRelationFixer} for a bidirectionnal relation where the attribute is a {@link Collection}
*
* @param setter the method that sets the {@link Collection} onto the target bean
* @param getter the method that gets the {@link Collection} from the target bean
* @param collectionFactory a supplier of an instance to fill the relation if it is null
* @param reverseSetter the setter for the other side of the relation
* @return a {@link BeanRelationFixer} that will add the input to the Collection and create if the getter returns null
*/
static <E, I, C extends Collection<I>> BeanRelationFixer<E, I> of(BiConsumer<E, C> setter, Function<E, C> getter, Supplier<C> collectionFactory,
BiConsumer<I, E> reverseSetter) {
return ofAdapter(setter, getter, collectionFactory, (target, input, collection) -> {
collection.add(input);
// bidirectional assignment
reverseSetter.accept(input, target);
});
}
/**
* Shortcut to create a {@link BeanRelationFixer} for a relation where the attribute is a {@link Collection}
*
* @param setter the method that sets the {@link Collection} onto the target bean
* @param getter the method that gets the {@link Collection} from the target bean
* @param collectionFactory a supplier of an instance to fill the relation if it is null
* @param adapter the final method that will be applied to bean, input and collection, expected to have at least a collection add, with eventual input adaptation
* @return a {@link BeanRelationFixer} that will add the input to the Collection and create if the getter returns null
*/
static <E, I, C extends Collection<?>> BeanRelationFixer<E, I> ofAdapter(BiConsumer<E, C> setter,
Function<E, C> getter,
Supplier<C> collectionFactory,
TriConsumer<E, I, C> adapter) {
return (target, input) -> {
C collection = getter.apply(target);
if (collection == null) {
// we fill the relation
collection = collectionFactory.get();
setter.accept(target, collection);
}
adapter.accept(target, input, collection);
};
}
/**
* Shortcut to create a {@link BeanRelationFixer} for a relation where the attribute is a {@link Map}
*
* @param setter the method that sets the {@link Map} onto the target bean
* @param getter the method that gets the {@link Map} from the target bean
* @param mapFactory a supplier of an instance to fill the relation if it is null
* @param adapter the final method that will be applied to bean, input and collection, expected to have at least a collection add, with eventual input adaptation
* @return a {@link BeanRelationFixer} that will add the input to the Collection and create if the getter returns null
*/
static <E, I, M extends Map<?, ?>> BeanRelationFixer<E, I> ofMapAdapter(BiConsumer<E, M> setter,
Function<E, M> getter,
Supplier<M> mapFactory,
TriConsumer<E, I, M> adapter) {
return (target, input) -> {
M map = getter.apply(target);
if (map == null) {
// we fill the relation
map = mapFactory.get();
setter.accept(target, map);
}
adapter.accept(target, input, map);
};
}
}